// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: xguo

#include "coin2/strategy/order_executor/agg_executor.h"

#include <algorithm>

#include "coin2/strategy/order_executor/util.h"

namespace coin2::strategy::order_executor {

bool AggOrderExecutor::ManageAggOrders() {
  UpdateState();
  ResetAction();
  CancelRiskOrders();
  return true;
}

bool AggOrderExecutor::SubmitAggOrders(
    OrderSide order_side,
    double order_price,
    double order_qty,
    const std::string& tag) {
  UpdateState();
  ResetAction();
  int64_t timestamp = GetCurrentTimestamp();

  if (order_side == OrderSide::BUY_ORDER) {
    return TryAggBuy(timestamp, order_price, order_qty);
  } else if (order_side == OrderSide::SELL_ORDER) {
    return TryAggSell(timestamp, order_price, order_qty);
  } else {
    NOTREACHED() << "Unknown order side!" << static_cast<int>(order_side);
    return false;
  }
}

bool AggOrderExecutor::CancelRiskOrders() {
  ManageRisk(GetCurrentTimestamp());
  for (auto order_id : cancel_executor_->GetCancelBuyList()) {
    os_->gateway()->CancelOrder(order_id);
  }
  for (auto order_id : cancel_executor_->GetCancelSellList()) {
    os_->gateway()->CancelOrder(order_id);
  }
  return true;
}

int AggOrderExecutor::CheckInput(double price, double qty) {
  // if price is wrong, do not fire.
  if (price <= 0) {
    return 12;
  }

  // if qty is wrong, do not fire.
  if (qty <= 0) {
    return 5;
  }

  // if qty is wrong, do not fire.
  if (qty > config_.lot_size() * 10) {
    return 15;
  }

  return 0;
}

int AggOrderExecutor::UpdateBuyOrder(int64_t timestamp, double buy_price, double buy_qty) {
  /* -----------------------
  # check in the order of:
  # input check
  # pos range check
  # inflight order check
  # similarity check
  # throttle
  * -------------------------*/

  int err_code = CheckInput(buy_price, buy_qty);
  if (err_code != 0) {
    return err_code;
  }

  /*-------------- pos range check --------------------------*/
  // If current position is too big, do not fire.
  if (GetPosition() > config_.max_pos()) {
    return 1;
  }

  // If current position is too big, do not fire.
  if (GetPosition() + buy_qty > config_.max_pos()) {
    return 1;
  }

  // If potential position is too big, do not fire.
  if (wo_->GetBuyPotential() + GetPosition() + buy_qty > config_.max_pos()) {
    return 2;
  }

  // If buy potential is too big, do not fire.
  if (wo_->GetBuyPotential() > config_.lot_size() * config_.max_working_cnt()) {
    return 7;
  }

  /*------------------- force_fire -------------------------*/
  if (config_.force_fire()) {
    fire_buy_ = true;
    return 0;
  }

  /*------------------ inflight order check ---------------------*/
  // If number of inflight orders are too much, do not fire.
  if (config_.allow_fast_order()) {
    if (wo_->GetBuyUnackCount() > config_.max_inflight_cnt() * 10) {
      return 8;
    }

    if (wo_->GetBuyUnackQty() > config_.lot_size() * config_.max_inflight_cnt()) {
      return 16;
    }
  } else {
    if (wo_->GetBuyUnackCount() > config_.max_inflight_cnt()) {
      return 8;
    }
  }

  /*--------------- similarity check -------------------------*/
  // If there is an working order that its price is same to buy_price,
  // keep it and do not fire.
  for (auto* order : wo_->GetBuyOrders()) {
    double price = order->order_price();
    if (std::abs(price - buy_price) < 1e-8) {
      return 3;
    }
  }

  /*----------------------- throttle -----------------------------*/
  // If within order_update_period, do not fire.
  if (timestamp - LastBuyTimestamp() < config_.order_update_period()) {
    return 11;
  }

  // fire
  fire_buy_ = true;
  return 0;
}

int AggOrderExecutor::UpdateSellOrder(int64_t timestamp, double sell_price, double sell_qty) {
  int err_code = CheckInput(sell_price, sell_qty);
  if (err_code != 0) {
    return err_code;
  }

  /* ------- pos range check ----------------*/
  // If current position is too small, do not fire.
  if (GetPosition() < config_.min_pos()) {
    return 1;
  }

  // If current position is too small, do not fire.
  if (GetPosition() - sell_qty < config_.min_pos()) {
    return 2;
  }

  // If potential position is too small, do not fire.
  if (wo_->GetSellPotential() + GetPosition() - sell_qty < config_.min_pos()) {
    return 2;
  }

  // If sell potential is too small, do not fire.
  if (-1 * wo_->GetSellPotential() > config_.lot_size() * config_.max_working_cnt()) {
    return 7;
  }

  /* -------- force_fire --------*/
  if (config_.force_fire()) {
    fire_sell_ = true;
    return 0;
  }

  /*--------- inflight order check */
  // If number of inflight orders are too much, do not fire.
  if (config_.allow_fast_order()) {
    if (wo_->GetSellUnackCount() > config_.max_inflight_cnt() * 10) {
      return 8;
    }

    if (wo_->GetSellUnackQty() > config_.lot_size() * config_.max_inflight_cnt()) {
      return 16;
    } else {
      if (wo_->GetSellUnackCount() > config_.max_inflight_cnt()) {
        return 8;
      }
    }
  }

  /*--------- similarity check */
  // If there is an order that its price is same to sell_price,
  // keep it and do not fire.
  for (auto* order : wo_->GetSellOrders()) {
    double price = order->order_price();
    if (std::abs(price - sell_price) < 1e-8) {
      return 3;
    }
  }

  /* ------ throttle -----------------*/
  // If within order_update_period, do not fire.
  if (timestamp - LastSellTimestamp() < config_.order_update_period()) {
    return 11;
  }

  /* ------  fire ------ */
  fire_sell_ = true;
  return 0;
}

bool AggOrderExecutor::TryAggBuy(int64_t timestamp, double buy_price, double buy_qty) {
  OrderSide order_side = OrderSide::BUY_ORDER;
  double order_price;
  double order_qty;

  // determine order price
  if (buy_price > 0) {
    order_price = buy_price;
  } else {
    order_price = best_ask_;
  }
  const auto& tick_price_policy = product_holder_->product_info().tick_price();
  order_price = tick_price_policy.RoundPassively(order_price, OrderDirection::DIRECTION_BUY);

  // Determine order size
  if (buy_qty > 0) {
    order_qty = buy_qty;
  } else {
    order_qty = config_.lot_size();
  }

  // position initially set to 0
  order_qty = std::min(order_qty, std::max(0.0, (config_.max_pos() - GetPosition())));

  if (order_qty < config_.lot_size() * config_.min_lot_size_ratio()) {
    order_qty = 0;
  }

  if (order_qty > config_.lot_size() * config_.max_lot_size_ratio()) {
    order_qty = config_.lot_size() * config_.max_lot_size_ratio();
  }

  if (order_qty > 0) {
    order_qty = product_holder_->product_info().tick_qty().Floor(order_qty);
  }

  // fire
  UpdateBuyOrder(timestamp, order_price, order_qty);
  if (fire_buy_ && order_qty > 0) {
    if (IsChinaFuturesPositionStyle(mea_)) {
      SubmitChinaOrder(timestamp, order_side, order_price, order_qty);
    } else {
      SubmitOrder(timestamp, order_side, order_price, order_qty);
    }
  }

  CancelRiskOrders();
  return fire_buy_;
}

bool AggOrderExecutor::TryAggSell(int64_t timestamp, double sell_price, double sell_qty) {
  OrderSide order_side = OrderSide::SELL_ORDER;
  double order_price = 0.0;
  double order_qty = 0.0;

  // determine order price
  if (sell_price > 0) {
    order_price = sell_price;
  } else {
    order_price = best_bid_;
  }

  const auto& tick_price_policy = product_holder_->product_info().tick_price();
  order_price = tick_price_policy.RoundPassively(order_price, OrderDirection::DIRECTION_SELL);

  // determine order size
  if (sell_qty > 0) {
    order_qty = sell_qty;
  } else {
    order_qty = config_.lot_size();
  }

  order_qty = std::min(order_qty, std::max(0., (GetPosition() - config_.min_pos())));

  if (order_qty < config_.lot_size() * config_.min_lot_size_ratio()) {
    order_qty = 0;
  }

  if (order_qty > config_.lot_size() * config_.max_lot_size_ratio()) {
    order_qty = config_.lot_size() * config_.max_lot_size_ratio();
  }

  if (order_qty > 0) {
    order_qty = product_holder_->product_info().tick_qty().Floor(order_qty);
  }

  // fire
  UpdateSellOrder(timestamp, order_price, order_qty);
  if (fire_sell_ && order_qty > 0) {
    if (IsChinaFuturesPositionStyle(mea_)) {
      SubmitChinaOrder(timestamp, order_side, order_price, order_qty);
    } else {
      SubmitOrder(timestamp, order_side, order_price, order_qty);
    }
  }

  CancelRiskOrders();
  return fire_sell_;
}

void AggOrderExecutor::ManageRisk(int64_t timestamp) {
  double max_qty = config_.lot_size() * config_.max_working_cnt();
  cancel_executor_->CancelBuyWithRisk(timestamp, GetPosition(), config_.max_pos(), max_qty);
  cancel_executor_->CancelSellWithRisk(timestamp, GetPosition(), config_.min_pos(), max_qty);
  cancel_executor_->CancelOldOrders(timestamp, timestamp - config_.max_posting_period());
  cancel_executor_->RemoveDuplicates();
}

void AggOrderExecutor::SubmitOrder(
    int64_t timestamp,
    OrderSide order_side,
    double order_price,
    double order_qty) {
  constexpr bool post_only = false;
  OrderSpec order_spec(
      product(),
      OrderType::LIMIT_ORDER,
      order_side,
      OrderDuration::GTC_ORDER,
      order_price,
      order_qty,
      timestamp,
      post_only,
      config_.leverage(),
      "");
  os_->gateway()->SubmitOrder(order_spec);

  if (order_side == OrderSide::BUY_ORDER) {
    last_buy_ts_ = timestamp;
  } else if (order_side == OrderSide::SELL_ORDER) {
    last_sell_ts_ = timestamp;
  } else {
    NOTREACHED();
  }
}

void AggOrderExecutor::SubmitChinaOrder(
    int64_t timestamp,
    OrderSide order_side,
    double order_price,
    double order_qty) {
  constexpr bool post_only = false;
  OrderSide china_order_side;
  if (order_side == OrderSide::BUY_ORDER) {
    if (GetShortPosition() + wo_->GetBuyPotential() + order_qty <= 0.0) {
      china_order_side = OrderSide::BUY_CLOSE_ORDER;
    } else {
      china_order_side = OrderSide::BUY_OPEN_ORDER;
    }
    OrderSpec order_spec(
        product(),
        OrderType::LIMIT_ORDER,
        china_order_side,
        OrderDuration::GTC_ORDER,
        order_price,
        order_qty,
        timestamp,
        post_only,
        config_.leverage(),
        "");
    os_->gateway()->SubmitOrder(order_spec);
    last_buy_ts_ = timestamp;

  } else if (order_side == OrderSide::SELL_ORDER) {
    if (GetShortPosition() + wo_->GetSellPotential() + order_qty <= 0.0) {
      china_order_side = OrderSide::SELL_CLOSE_ORDER;
    } else {
      china_order_side = OrderSide::SELL_OPEN_ORDER;
    }
    OrderSpec order_spec(
        product(),
        OrderType::LIMIT_ORDER,
        china_order_side,
        OrderDuration::GTC_ORDER,
        order_price,
        order_qty,
        timestamp,
        post_only,
        config_.leverage(),
        "");
    os_->gateway()->SubmitOrder(order_spec);
    last_sell_ts_ = timestamp;

  } else {
    NOTREACHED();
  }
}

}  // namespace coin2::strategy::order_executor
